// Copyright Leonid Shlyapnikov 2010
// Distributed under the GNU Lesser General Public License http://www.gnu.org/licenses/lgpl.html
//    author: Leonid Shlyapnikov, created on: Feb 1, 2010

#include <boost/test/test_tools.hpp>
#include <boost/cstdint.hpp>
#include <boost/mpl/list.hpp>
#include <boost/foreach.hpp>
#include <sstream>

#include "../../main/fixparser/SimpleFixMessage.h"
#include "../../main/fixparser/MemoryArea.h"
#include "../../main/fixparser/Tag.h"
#include "../../main/fixparser/String.h"
#include "../../main/fixparser/FieldWriterVisitor.h"
#include "../../main/fixparser/util.h"
#include "../../main/fixparser/log.h"

using fixparser::Tag;
using fixparser::MemoryArea;
using fixparser::SimpleFixMessage;
using fixparser::Field;
using fixparser::FieldList;
using fixparser::newString;
using fixparser::String;
using fixparser::static_strlen;


struct CachedTags {
    typedef boost::mpl::list<Tag<100> , Tag<101> , Tag<102> , Tag<103> , Tag<104> >::type cached_tags_mpl_list;
};

struct CachedTags2 {
    typedef boost::mpl::list<Tag<8> , Tag<9>, Tag<35>, Tag<49>, Tag<56>, Tag<34> >::type cached_tags_mpl_list;
};

struct MsgTypeDCachedTags {
    typedef boost::mpl::list<Tag<8>, Tag<9>, Tag<35>, Tag<49>, Tag<56>, Tag<34>, Tag<11>, Tag<78> >::type cached_tags_mpl_list;
};


//void SimpleFixMessage_isEmptyField() {
//    // GIVEN
//    MemoryArea ma(1024);
//
//    typedef fixparser::SimpleFixMessage<CachedTags> FixMessageType;
//    const int numberOfCachedFields = FixMessageType::NUMBER_OF_CACHED_FIELDS;
//    BOOST_CHECK_EQUAL(numberOfCachedFields, 5);
//
//    // WHEN
//    FixMessageType* msg = ma.create<FixMessageType>();
//    const size_t maSize = ma.size();
//
//    // THEN
//    BOOST_CHECK_EQUAL(0, msg->checksum().length());
//    BOOST_CHECK_EQUAL(0, reinterpret_cast<int64_t>(msg->checksum().c_str()));
//    BOOST_CHECK_EQUAL(true, msg->isFieldEmpty<100>());
//    BOOST_CHECK_EQUAL(true, msg->isFieldEmpty<101>());
//    BOOST_CHECK_EQUAL(true, msg->isFieldEmpty<102>());
//    BOOST_CHECK_EQUAL(true, msg->isFieldEmpty<103>());
//    BOOST_CHECK_EQUAL(true, msg->isFieldEmpty<104>());
//    const String& str0 = msg->value<101>();
//    const String& str1 = msg->value<101>();
//    BOOST_CHECK_EQUAL(0, str0.length());
//    BOOST_CHECK_EQUAL(0, str1.length());
//    BOOST_CHECK_EQUAL(&str0, &str1); // the same empty string
//    BOOST_CHECK_EQUAL(ma.size(), maSize); //  memoryArea size did not change, nothing new allocated
//}

void SimpleFixMessage_isFieldSet() {
    // GIVEN
    MemoryArea ma(1024);

    typedef fixparser::SimpleFixMessage<CachedTags> FixMessageType;
    const int numberOfCachedFields = FixMessageType::NUMBER_OF_CACHED_FIELDS;
    BOOST_CHECK_EQUAL(numberOfCachedFields, 5);

    // WHEN
    FixMessageType* msg = ma.create<FixMessageType>();
    const size_t maSize = ma.size();

    // THEN
    BOOST_CHECK_EQUAL(0, msg->checksum().length());
    BOOST_CHECK_EQUAL(0, reinterpret_cast<int64_t>(msg->checksum().c_str()));

    BOOST_CHECK_EQUAL(false, msg->isFieldSet<100>());
    BOOST_CHECK_THROW(msg->value<100>(), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field<100>(), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldSet<101>());
    BOOST_CHECK_THROW(msg->value<101>(), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field<101>(), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldSet<103>());
    BOOST_CHECK_THROW(msg->value<103>(), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field<103>(), std::invalid_argument);

    BOOST_CHECK_EQUAL(false, msg->isFieldSet<104>());
    BOOST_CHECK_THROW(msg->value<104>(), std::invalid_argument);
    BOOST_CHECK_THROW(msg->field<104>(), std::invalid_argument);

    BOOST_CHECK_EQUAL(ma.size(), maSize); //  memoryArea size did not change, nothing new allocated
}

void SimpleFixMessage_createEmptyMessage() {
    // GIVEN
    MemoryArea ma(1024);

    typedef fixparser::SimpleFixMessage<CachedTags> FixMessageType;
    const int numberOfCachedFields = FixMessageType::NUMBER_OF_CACHED_FIELDS;
    BOOST_CHECK_EQUAL(numberOfCachedFields, 5);

    // WHEN
    const FixMessageType* emptyMsg = ma.create<FixMessageType>();

    // THEN
    BOOST_CHECK_NE(0, reinterpret_cast<int64_t>(emptyMsg));
    // size of message and size of checksum tag ID
    BOOST_CHECK(ma.size() > 0);
}

void SimpleFixMessage_createMessage() {
    // GIVEN
    MemoryArea ma(1024);

    typedef fixparser::SimpleFixMessage<CachedTags> FixMessageType;
    const int numberOfCachedFields = FixMessageType::NUMBER_OF_CACHED_FIELDS;
    BOOST_CHECK_EQUAL(numberOfCachedFields, 5);

    FixMessageType* msg = ma.create<FixMessageType>();
    BOOST_CHECK_EQUAL(msg->numberOfFields(), 0);

    size_t expectedMaSize = ma.size();
    size_t expectedNumberOfFields = 0;

    {
        // WHEN
        Field* f0 = ma.create<Field>();
        f0->setTag("8");
        f0->setValue("FIX.4.2");
        // THEN
        expectedMaSize += sizeof(Field) + static_strlen("8") + 1 + static_strlen("FIX.4.2") + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        // WHEN
        msg->addField(f0); // should NOT allocate any extra memory
        ++expectedNumberOfFields;
        // THEN
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
    }
    {
        // WHEN
        Field* f1 = ma.create<Field> ();
        f1->setTag("9");
        f1->setValue("99");
        // THEN
        expectedMaSize += sizeof(Field) + static_strlen("9") + 1 + static_strlen("99") + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        // WHEN
        msg->addField(f1); // should NOT allocate any extra memory
        ++expectedNumberOfFields;
        // THEN
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
    }
    {
        const String* str35 = newString(&ma, "35");
        const String* strD = newString(&ma, "D");
        expectedMaSize += 2 * sizeof(String) + static_strlen("35") + 1 + static_strlen("D") + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize); // 2 String objects allocated + content

        msg->addField(*str35, *strD);
        ++expectedNumberOfFields;
        expectedMaSize += sizeof(Field); // should allocate only new Field
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
    }
    {
        // GIVEN

        const char val0[] = "field100Value";
        const char val1[] = "newField100Value";

        const String* field100Value = newString(&ma, val0);
        const String* newField100Value = newString(&ma, val1);
        expectedMaSize += sizeof(String) + static_strlen(val0) + 1
                + sizeof(String) + static_strlen(val1) + 1;
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);

        // WHEN 1
        BOOST_CHECK_EQUAL(false, msg->isFieldSet<100>());
        msg->setValue<100>(*field100Value);
        ++expectedNumberOfFields;

        // THEN 1
        expectedMaSize += sizeof(Field) + static_strlen("100") + 1;  // only new field is allocated + tag string
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
        BOOST_CHECK_EQUAL(val0, msg->value<100>().c_str());

        // WHEN 2
        msg->setValue<100>(*newField100Value); // field already exists

        // THEN 2
        // size did not change: field, tag string and value string are already allocated
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
        BOOST_CHECK_EQUAL(val1, msg->value<100>().c_str());
        BOOST_CHECK_EQUAL(true, msg->isFieldSet<100>());

        // WHEN 3
        msg->setValue<100>(*field100Value); // field already exists

        // THEN 3
        // size did not change: field, tag string and value string are already allocated
        BOOST_CHECK_EQUAL(ma.size(), expectedMaSize);
        BOOST_CHECK_EQUAL(msg->numberOfFields(), expectedNumberOfFields);
        BOOST_CHECK_EQUAL(expectedNumberOfFields, 4); // just double checking
        BOOST_CHECK_EQUAL(val0, msg->value<100>().c_str());
    }
}

void SimpleFixMessage_writeTo() {
    // GIVEN
    MemoryArea ma(1024);
    typedef fixparser::SimpleFixMessage<CachedTags> FixMessageType;
    FixMessageType* msg = ma.create<FixMessageType>();
    {
        Field* f0 = ma.create<Field>();
        f0->setTag("8");
        f0->setValue("FIX.4.2");
        msg->addField(f0); // should NOT allocate any extra memory
    }
    {
        Field* f1 = ma.create<Field> ();
        f1->setTag("9");
        f1->setValue("99");
        msg->addField(f1);
    }
    {
        const String* str35 = newString(&ma, "35");
        const String* strD = newString(&ma, "D");
        msg->addField(*str35, *strD);
    }
    {
        const String* field100Value = newString(&ma, "field100Value");
        msg->setValue<100>(*field100Value);
    }
    {
        const String* field101Value = newString(&ma, "field101Value");
        msg->setValue<101>(*field101Value);
    }
    {
        const String* checksumVal = newString(&ma, "123");
        msg->setChecksum(*checksumVal);
    }

    // WHEN
    std::stringstream ss;
    fixparser::FieldWriterVisitor<std::stringstream> fieldWriterVisitor(&ss);
    msg->acceptVisitor(&fieldWriterVisitor);

    // THEN
    std::stringstream expectedSs;
    expectedSs << "8=FIX.4.2";
    expectedSs.put(0x01);
    expectedSs << "9=99";
    expectedSs.put(0x01);
    expectedSs << "35=D";
    expectedSs.put(0x01);
    expectedSs << "100=field100Value";
    expectedSs.put(0x01);
    expectedSs << "101=field101Value";
    expectedSs.put(0x01);
    expectedSs << "10=123";
    expectedSs.put(0x01);
//    LOG_INFO("ss: " << ss.str() << ", expected: "  << expectedSs.str());
    BOOST_CHECK_EQUAL(ss.str(), expectedSs.str());
}

void SimpleFixMessage_setChecksum1() {
    // GIVEN

    MemoryArea ma(2048);
    typedef fixparser::SimpleFixMessage<CachedTags2> FixMessageType;

    // 8=FIX.4.2|9=65|35=A|49=SERVER|56=CLIENT|34=177|52=20090107-18:15:16|98=0|108=30|10=062|

    FixMessageType* msg = ma.create<FixMessageType>();
    msg->setValue<8>(newStringConst(&ma, "FIX.4.2")); // using fixparser::String
    msg->setValue<9>(std::string("65")); // using std::string
    msg->setValue<35>("B"); // using const char* (zero-terminated string)
    msg->setValue<35>("A");
    msg->setValue<49>("SERVER");
    msg->setValue<56>("CLIENT");
    msg->setValue<34>("177");
    msg->addField("52", "20090107-18:15:16");
    msg->addField("98", "0");
    msg->addField("108", "30");

    // WHEN
    BOOST_TEST_CHECKPOINT("WHEN");
    msg->setChecksum();

    // THEN
    BOOST_CHECK_EQUAL(msg->checksum().c_str(), "062");
}

void SimpleFixMessage_accessRepeatingGroup() {
    // GIVEN
    BOOST_TEST_CHECKPOINT("GIVEN");

    MemoryArea ma(2048);
    typedef fixparser::SimpleFixMessage<MsgTypeDCachedTags> FixMessageTypeD;
    FixMessageTypeD* msg = ma.create<FixMessageTypeD>();

    msg->setValue<8>("FIX.4.2");
    msg->setValue<9>("99");
    msg->setValue<35>("D");
    msg->addField("100", "field100Value");
    msg->addField("101", "field101Value");

    BOOST_CHECK_EQUAL(false, msg->isFieldSet<78>());

    // repeating group
    msg->setValue<78>("2");
    msg->addField("79", "AllocAccount1");
    msg->addField("80", "AllocShares1");
    msg->addField("79", "AllocAccount2");
    msg->addField("80", "AllocShares2");

    // THEN
    BOOST_TEST_CHECKPOINT("THEN");
    const fixparser::String& noAllocs = msg->value<78>();
    BOOST_CHECK_EQUAL(noAllocs.c_str(), "2");

    //msg->value<9>(); // TODO should be recalculated????

}
